
本节中，我探讨如何在LLVM中实现一个在指令选择后运行的新机器函数通道。将创建一个MachineFunctionPass类，它是LLVM中原始FunctionPass类的一个子集，可以与opt一起运行。该类调整了原始基础设施，以通过llc在后端对MachineFunction表示进行操作的通道实现。

后端中的通道实现利用遗留的通道管理器接口，而不是新的通道管理器。因为LLVM目前在后端，没有一个完整的新通道管理器的工作实现，所以本章将遵循在遗留通道管理器流水线中添加新通道的方法。

实现方面，机器函数通道一次优化单个(机器)函数，但不是覆写runOnFunction()方法，而是覆写runOnMachineFunction()方法。本节将实现的机器函数通道是一个通道，用于检查除零行为，特别是插入后端捕获的代码。由于MC88100上的硬件限制，因为该CPU不能可靠地检测除零情况，所以这种类型的通道对于M88k目标很重要。

继续上一章的后端实现，来看看后端机器函数通道是如何实现的!

\mySubsubsection{13.1.1.}{实现了M88k目标的顶层接口}

首先，在llvm/lib/Target/M88k/M88k.h中，在llvm命名空间声明中添加两个原型，以便稍后使用:

\begin{enumerate}
\item
将要实现的机器函数通道称为M88kDivInstrPass，将添加一个函数声明来初始化这个通道，并接受通道注册表，这是一个管理所有通道的注册和初始化的类:

\begin{cpp}
void initializeM88kDivInstrPass(PassRegistry &);
\end{cpp}

\item
接下来，声明创建M88kDivInstr通道的实际函数，将M88k目标机器信息作为其参数:

\begin{cpp}
FunctionPass *createM88kDivInstr(const M88kTargetMachine &);
\end{cpp}
\end{enumerate}

\mySubsubsection{13.1.2.}{为机器函数通道添加TargetMachine实现}

接下来，我们将分析llvm/lib/Target/M88k/M88kTargetMachine.cpp中需要进行的一些更改:

\begin{enumerate}
\item
LLVM中，通常为用户提供打开或关闭通道选项，所以为用户提供与机器功能通道相同的灵活性。首先声明一个名为m88k-no-check-zero-division的命令行选项，并将其初始化为false，所以除非用户显式地将其关闭，否则将始终检查是否有零除法。我们把它添加到llvm命名空间声明中，并且是llc的一个选项:

\begin{cpp}
using namespace llvm;
static cl::opt<bool>
    NoZeroDivCheck("m88k-no-check-zero-division", cl::Hidden,
                    cl::desc("M88k: Don't trap on integer division by zero."),
                    cl::init(false));
\end{cpp}

\item
还创建一个返回命令行值的正式方法，以便查询它以确定是否将运行该通道。我们最初的命令行选项将封装在noZeroDivCheck()方法中，以便稍后可以使用命令行结果:

\begin{cpp}
M88kTargetMachine::~M88kTargetMachine() {}
bool M88kTargetMachine::noZeroDivCheck() const { return NoZeroDivCheck; }
\end{cpp}

\item
接下来，在LLVMInitializeM88kTarget()中，M88k目标和通道注册和初始化，将插入对initializeM88kDivInstrPass()的调用，该方法先前在llvm/lib/target/M88k/M88k.h中声明:

\begin{cpp}
extern "C" LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeM88kTarget() {
    RegisterTargetMachine<M88kTargetMachine> X(getTheM88kTarget());
    auto &PR = *PassRegistry::getPassRegistry();
    initializeM88kDAGToDAGISelPass(PR);
    initializeM88kDivInstrPass(PR);
}
\end{cpp}

\item
M88k目标还需要覆盖addMachineSSAOptimization()，这是一个方法，当机器指令是SSA形式时，添加通道来优化机器指令。我们的机器函数通道是作为一种机器SSA优化添加的，此方法声明为一个将覆写的函数。将在M88kTargetMachine.cpp的末尾添加完整的实现:

\begin{cpp}
    bool addInstSelector() override;
    void addPreEmitPass() override;
    void addMachineSSAOptimization() override;

. . .
void M88kPassConfig::addMachineSSAOptimization() {
    addPass(createM88kDivInstr(getTM<M88kTargetMachine>()));
    TargetPassConfig::addMachineSSAOptimization();
}
\end{cpp}

\item
我们的方法返回命令行选项来切换机器函数传递(noZeroDivCheck()方法)也在M88kTargetMachine.h中声明:

\begin{cpp}
    ~M88kTargetMachine() override;
    bool noZeroDivCheck() const;
\end{cpp}
\end{enumerate}

\mySubsubsection{13.1.3.}{开发具体的机器功能通道}

现在，完成了在M88k目标机上的实现，下一步将是开发机器功能通道本身。实现包含在新文件llvm/lib/Target/M88k/m88kdivinstrument.cpp中:

\begin{enumerate}
\item
首先，添加机器函数通道所需的头文件。这包括访问M88k目标信息的头文件和允许操作机器函数和机器指令的头文件:

\begin{cpp}
#include "M88k.h"
#include "M88kInstrInfo.h"
#include "M88kTargetMachine.h"
#include "MCTargetDesc/M88kMCTargetDesc.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Support/Debug.h"
\end{cpp}

\item
之后，将添加一些代码来准备我们的机器函数通道。第一个是名为m88k-div-instr的DEBUG\_TYPE定义，用于调试时的细粒度控制。定义这个DEBUG\_TYPE允许用户指定机器函数的通道名称，并在调试信息启用时查看与该通道相关的调试信息:

\begin{cpp}
#define DEBUG_TYPE "m88k-div-instr"
\end{cpp}

\item
还指定使用llvm名称空间，并为机器函数声明一个STATISTIC值。这个统计数据称为InsertedChecks，其跟踪编译器插入了多少个除零检查。最后，声明一个匿名命名空间来封装后续的机器函数通道实现:

\begin{cpp}
using namespace llvm;
STATISTIC(InsertedChecks, "Number of inserted checks for
division by zero");
namespace {
\end{cpp}

\item
这个机器函数通道的目的是检查除零的情况，并插入会导致CPU陷入陷阱的指令。这些指令需要条件码，所以们称之为CC0的枚举值定义了对M88k目标有效的条件码，以及其编码:

\begin{cpp}
enum class CC0 : unsigned {
    EQ0 = 0x2,
    NE0 = 0xd,
    GT0 = 0x1,
    LT0 = 0xc,
    GE0 = 0x3,
    LE0 = 0xe
};
\end{cpp}

\item
接下来，让为机器函数通道创建实际的类，称为M88kDivInstr。首先，将其创建为继承MachineFunctionPass类型的实例，再声明M88kDivInstr传递将需要的各种必要实例。这包括M88kBuilder(将在后面创建并详细介绍)和M88kTargetMachine(包含目标指令和寄存器信息)。生成指令时，还需要寄存器库信息和机器寄存器信息。还添加了一个AddZeroDivCheck布尔值来表示前面的命令行选项，可以打开或关闭通道:

\begin{cpp}
class M88kDivInstr : public MachineFunctionPass {
    friend class M88kBuilder;
    const M88kTargetMachine *TM;
    const TargetInstrInfo *TII;
    const TargetRegisterInfo *TRI;
    const RegisterBankInfo *RBI;
    MachineRegisterInfo *MRI;
    bool AddZeroDivCheck;
\end{cpp}

\item
对于M88kDivInstr类的公共变量和方法，我们声明了一个标识号，LLVM将使用它来标识我们的通道，以及M88kDivInstr构造函数，接受M88kTargetMachine。接下来，覆写getRequiredProperties()方法，该方法表示MachineFunction在优化过程中可能具有的属性，还要覆写runOnMachineFunction()方法，这是通道检查除零时运行的主要方法之一。第二个公开声明的重要函数是runOnMachineBasicBlock()函数，将从runOnMachineFunction()内部执行:

\begin{cpp}
public:
    static char ID;
    M88kDivInstr(const M88kTargetMachine *TM = nullptr);
    MachineFunctionProperties getRequiredProperties() const override;
    bool runOnMachineFunction(MachineFunction &MF) override;
    bool runOnMachineBasicBlock(MachineBasicBlock &MBB);
\end{cpp}

\item
最后，最后一部分是声明私有方法和关闭类。在M88kDivInstr类中声明的唯一私有方法是addZeroDivCheck()方法，在除法指令之后插入对除零的检查。正如稍后将看到的，MachineInstr将需要指向M88k目标上的特定分割指令:

\begin{cpp}
private:
    void addZeroDivCheck(MachineBasicBlock &MBB, MachineInstr *DivInst);
};
\end{cpp}

\item
接下来创建一个M88kBuilder类，是一个构建器实例，用于创建特定于M88k的指令。这个类保存了MachineBasicBlock的一个实例(和一个相应的迭代器)，以及DebugLoc来跟踪这个构建器类的调试位置。其他必要的实例包括M88k目标机的目标指令信息、目标寄存器信息和寄存器库信息：

\begin{cpp}
class M88kBuilder {
    MachineBasicBlock *MBB;
    MachineBasicBlock::iterator I;
    const DebugLoc &DL;
    const TargetInstrInfo &TII;
    const TargetRegisterInfo &TRI;
    const RegisterBankInfo &RBI;
\end{cpp}

\item
对于M88kBuilder类的公共方法，必须实现该构造器的构造函数。初始化时，构建器需要一个M88kDivInstr传递的实例来初始化目标指令、寄存器信息和寄存器库信息，以及MachineBasicBlock和调试位置:

\begin{cpp}
public:
    M88kBuilder(M88kDivInstr &Pass, MachineBasicBlock *MBB, const DebugLoc &DL)
        : MBB(MBB), I(MBB->end()), DL(DL), TII(*Pass.TII), TRI(*Pass.TRI), RBI(*Pass.RBI) {}
\end{cpp}

\item
接下来，在M88k构造器中创建了一个方法来设置MachineBasicBlock，并且相应地设置了MachineBasicBlock迭代器:

\begin{cpp}
void setMBB(MachineBasicBlock *NewMBB) {
    MBB = NewMBB;
    I = MBB->end();
}
\end{cpp}

\item
接下来需要实现constraininstt()函数，并使用它处理MachineInstr实例。对于给定的MachineInstr，检查MachineInstr实例的操作数的寄存器类，是否可以通过已有的函数constrainSelectedInstRegOperands()来约束。如下所示，这个机器函数通道可以约束机器指令的寄存器操作数:

\begin{cpp}
void constrainInst(MachineInstr *MI) {
    if (!constrainSelectedInstRegOperands(*MI, TII, TRI, RBI))
    llvm_unreachable("Could not constrain register operands");
}
\end{cpp}

\item
该通道插入的指令之一是BCND指令，如M88kInstrInfo中定义的那样，是M88k目标上的一个条件分支。为了创建这个指令，需要一个条件代码，是在m88kdivinstrument.cpp开头实现的CC0枚举——一个寄存器和MachineBasicBlock。BCND指令只是在创建和检查新创建的指令是否可以约束后返回:

\begin{cpp}
MachineInstr *bcnd(CC0 Cc, Register Reg, MachineBasicBlock
    *TargetMBB) {
    MachineInstr *MI = BuildMI(*MBB, I, DL, TII.get(M88k::BCND))
                            .addImm(static_cast<int64_t>(Cc))
                            .addReg(Reg)
                            .addMBB(TargetMBB);
    constrainInst(MI);
    return MI;
}
\end{cpp}

\item
类似地，也需要一个trap指令用于我们的机器函数通道，这是一个TRAP503指令。该指令需要一个寄存器，若寄存器的第0位没有设置，则会引发TRAP503陷阱，该陷阱将在除零后引发。创建TRAP503指令时，在返回TRAP503之前会检查是否存在约束。此外，这结束了M88kBuilder类的类实现，并完成了前面声明的匿名命名空间:

\begin{cpp}
    MachineInstr *trap503(Register Reg) {
        MachineInstr *MI = BuildMI(*MBB, I, DL, TII.
            get(M88k::TRAP503)).addReg(Reg);
        constrainInst(MI);
        return MI;
    }
};
} // end anonymous namespace
\end{cpp}

\item
现在可以开始实现在机器函数通道中，执行实际检查的函数。首先，探索一下addZeroDivCheck()是如何实现的。该函数只是在当前机器指令之间插入一个除零检查，该指令应该指向DIVSrr或DIVUrr，这些分别是有符号除法和无符号除法的助记符。插入BCND和TRAP503指令，InsertedChecks统计量加1表示添加了两条指令:

\begin{cpp}
void M88kDivInstr::addZeroDivCheck(MachineBasicBlock &MBB,
                                   MachineInstr *DivInst) {
    assert(DivInst->getOpcode() == M88k::DIVSrr ||
            DivInst->getOpcode() == M88k::DIVUrr && "Unexpected
            opcode");
    MachineBasicBlock *TailBB = MBB.splitAt(*DivInst);
    M88kBuilder B(*this, &MBB, DivInst->getDebugLoc());
    B.bcnd(CC0::NE0, DivInst->getOperand(2).getReg(), TailBB);
    B.trap503(DivInst->getOperand(2).getReg());
    ++InsertedChecks;
}
\end{cpp}

\item
接下来实现runOnMachineFunction()，在LLVM中创建函数传递类型时要覆写的重要函数之一。此函数返回true或false，取决于在机器函数传递期间是否进行了任何更改。对于给定的机器功能，收集所有相关的M88k子目标信息，包括目标指令、目标寄存器、寄存器库和机器寄存器信息。关于用户是否打开或关闭M88kDivInstr机器功能传递的详细信息也会查询并存储在AddZeroDivCheck变量中，需要对机器功能中的所有机器基本块进行除零分析。执行机器基本块分析的函数是runOnMachineBasicBlock()，接下来将实现这一点。最后，若机器函数发生了变化，则由返回的changed变量表示:

\begin{cpp}
bool M88kDivInstr::runOnMachineFunction(MachineFunction &MF) {
    const M88kSubtarget &Subtarget =
    MF.getSubtarget<M88kSubtarget>();
    TII = Subtarget.getInstrInfo();
    TRI = Subtarget.getRegisterInfo();
    RBI = Subtarget.getRegBankInfo();
    MRI = &MF.getRegInfo();
    AddZeroDivCheck = !TM->noZeroDivCheck();
    bool Changed = false;
    for (MachineBasicBlock &MBB : reverse(MF))
        Changed |= runOnMachineBasicBlock(MBB);
    return Changed;
}
\end{cpp}

\item
对于runOnMachineBasicBlock()函数，还返回一个Changed布尔标志来指示机器基本块是否已更改，但最初设置为false。此外，在一个机器基本块中，需要分析所有的机器指令，并检查这些指令是否分别是DIVUrr或DIVSrr操作码。除了检查操作码是否为分割指令外，还需要检查用户是否打开或关闭了机器功能通道。若满足所有这些条件，则用条件分支进行除零检查，并通过前面实现的addZeroDivCheck()函数相应地添加陷阱指令。

\begin{cpp}
bool M88kDivInstr::runOnMachineBasicBlock(MachineBasicBlock
&MBB) {
    bool Changed = false;
    for (MachineBasicBlock::reverse_instr_iterator I =
    MBB.instr_rbegin();
            I != MBB.instr_rend(); ++I) {
        unsigned Opc = I->getOpcode();
        if ((Opc == M88k::DIVUrr || Opc == M88k::DIVSrr) &&
        AddZeroDivCheck) {
            addZeroDivCheck(MBB, &*I);
            Changed = true;
        }
    }
    return Changed;
}
\end{cpp}

\item
之后，需要实现构造函数来初始化函数通道，并设置适当的机器函数属性。这可以通过调用initializem88kdivinstpass()函数来实现，在M88kDivInstr类的构造函数中使用PassRegistry实例，也可以通过设置机器函数属性来说明通道需要机器函数是SSA形式:

\begin{cpp}
M88kDivInstr::M88kDivInstr(const M88kTargetMachine *TM)
: MachineFunctionPass(ID), TM(TM) {
    initializeM88kDivInstrPass(*PassRegistry::getPassRegistry());
}
MachineFunctionProperties M88kDivInstr::getRequiredProperties()
const {
    return MachineFunctionProperties().set(
        MachineFunctionProperties::Property::IsSSA);
}
\end{cpp}

\item
下一步是初始化机器函数传递的ID，并使用机器函数通道的详细信息实例化INITIALIZE\_PASS宏。这需要通道实例、命名信息和两个布尔参数，用于指示通道是否只检查CFG，以及通道是否为分析通道。由于M88kDivInstr不执行这两种操作，因此为通道初始化宏指定了两个false参数:

\begin{cpp}
char M88kDivInstr::ID = 0;
INITIALIZE_PASS(M88kDivInstr, DEBUG_TYPE, "Handle div instructions", false, false)
\end{cpp}

\item
最后，createM88kDivInstr()函数使用M88kTargetMachine实例创建M88kDivInstr通道的新实例，将封装到llvm命名空间中，并在完成此函数后结束命名空间:

\begin{cpp}
namespace llvm {
FunctionPass *createM88kDivInstr(const M88kTargetMachine &TM) {
    return new M88kDivInstr(&TM);
}
} // end namespace llvm
\end{cpp}
\end{enumerate}


\mySubsubsection{13.1.4.}{构建新实现的机器函数通道}

我们几乎完成了实现新的机器函数通道!现在，需要确保CMake意识到m88kdivinst.cpp中的新机器功能通道。然后，将该文件添加到llvm/lib/Target/M88k/CMakeLists.txt中:

\begin{cmake}
add_llvm_target(M88kCodeGen
    M88kAsmPrinter.cpp
    M88kDivInstr.cpp
    M88kFrameLowering.cpp
    M88kInstrInfo.cpp
    M88kISelDAGToDAG.cpp
\end{cmake}

最后一步是用以下命令用我们的新机器函数通道实现构建LLVM，需要CMake选项-DLLVM\_EXPERIMENTAL\_TARGETS\_TO\_BUILD=M88k来构建M88k目标:

\begin{shell}
$ cmake -G Ninja ../llvm-project/llvm -DLLVM_EXPERIMENTAL_TARGETS_TO_
BUILD=M88k -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="llvm"
$ ninja
\end{shell}

这样，就实现了机器函数通道，但看它是如何工作的不是很有趣吗?我们可以通过llc传递LLVM IR来演示此通道的结果。

\mySubsubsection{13.1.5.}{使用llc运行机器功能通道}

我们有下面的IR，包含一个除以0的除法:

\begin{shell}
$ cat m88k-divzero.ll
target datalayout = "E-m:e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-
i32:32:32-i64:64:64-f32:32:32-f64:64:64-a:8:16-n32"
target triple = "m88k-unknown-openbsd"

@dividend = dso_local global i32 5, align 4
define dso_local i32 @testDivZero() #0 {
    %1 = load i32, ptr @dividend, align 4
    %2 = sdiv i32 %1, 0
    ret i32 %2
}
\end{shell}

把它输入llc:

\begin{shell}
$ llc m88k-divzero.ll
\end{shell}

结果汇编中，默认情况下，除零检查，用bnd.n(BCND)和t0(TRAP503)表示，由我们的新机函数通道插入:

\begin{shell}
| %bb.1:
    subu %r2, %r0, %r2
    bcnd.n ne0, %r0, .LBB0_2
    divu %r2, %r2, 0
    tb0 0, %r3, 503
. . .
.LBB0_3:
    bcnd.n ne0, %r0, .LBB0_4
    divu %r2, %r2, 0
    tb0 0, %r3, 503
\end{shell}

然而，来看看当为llc指定-{}-m88k-no-check-zero除法时会发生什么:

\begin{shell}
$ llc m88k-divzero.ll –m88k-no-check-zero-division
\end{shell}

这个后端选项指示llc不运行检查除零的传递，生成的汇编将不包含任何bnd或TRAP503指令。这里有一个例子:

\begin{shell}
| %bb.1:
    subu %r2, %r0, %r2
    divu %r2, %r2, 0
    jmp.n %r1
    subu %r2, %r0, %r2
\end{shell}

实现一个机器函数通道需要几个步骤，但是这些步骤可以作为指导方针，有助于实现任何类型的机器函数通道。我们已经在本节中广泛地探讨了后端，所以切换一下方向，看看前端是如何了解M88k目标的。















